﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace QuadRemote.Core
{
    class HEXBlock
    {
        public int address;
        public int length;

        public byte[][] rows;
        public Boolean[] rowContainsData;
        public int format;
        public int bytesPerRow;
        public int addressPerRow;

        public HEXBlock(int address, int format)
        {
            this.format = format;
            this.address = address;

            if (format == HEXReader.FORMAT_PIC33)
            {
                this.bytesPerRow = 192;
                this.addressPerRow = 128;
                this.length = 1024;
                this.rowContainsData = new Boolean[8];
                this.rows = new byte[8][];//,bytesPerRow];
                for (int i = 0; i < 8; i++)
                {
                    this.rowContainsData[i] = false;
                    this.rows[i] = new byte[192];
                    for (int j = 0; j < 192; j++)
                        rows[i][j] = 0xFF;
                }
            }
        }

        public void insert(byte[] bytes, int offset, int length, int address)
        {
            if (format == HEXReader.FORMAT_PIC33)
            {
                address -= this.address;

                int row = address / 128;

                int rowoffset = address % 128;
                rowoffset = rowoffset * 3 / 2;

                rowContainsData[row] = true;
                rows[row][rowoffset] = bytes[offset + length - 3];
                rows[row][rowoffset + 1] = bytes[offset + length - 2];
                rows[row][rowoffset + 2] = bytes[offset + length - 1];
                //Array.Copy(bytes, offset, rows[row], rowoffset, length);
            }
        }
    }

    class HEXReader
    {
        private List<HEXBlock> pages;
        public static int FORMAT_PIC33 = 1;

        private static byte getByteFromHex(String chars)
        {
            byte ret = 0;
            char[] charArray = chars.ToCharArray();
            //Console.Write("[" + chars + ":");
            int shift = 4;

            for (int i=0;i<chars.Length;i++)
            {
                int v = 0;
                byte c = (byte)charArray[i];
                //Console.Write((int)c + ",");
                if (c >= 48 && c <= 57) // 0..9
                    v = c - 48;
                else if (c >= 65 && c <= 70) // ABCDEF
                    v = c - 65 + 10;
                else if (c >= 97 && c <= 102) // abcdef
                    v = c - 97 + 10;
                else
                    Console.WriteLine("Wrong char: " + (int)c);
                ret += (byte)(v << shift);
                shift -= 4;
            }
            //Console.Write("]");


            return ret;
        }

        private int format;

        private int bytesTotal = 0;
        public int getTotalBytes()
        {
            if (pages.Count > 0)
            {
                int total = 0;
                foreach (HEXBlock b in pages)
                {
                    for (int i = 0; i < b.rows.Length; i++)
                    {
                        if (b.rowContainsData[i])
                            total += b.bytesPerRow;
                    }
                }
                return total;
            }
            return 0;
            //return pages.Count > 0 ? pages.Count * pages[0].bytesPerRow * pages[0].rows.Length : 0;
        }

        public HEXReader(String filename, int format)
        {
            this.format = format;
            pages = new List<HEXBlock>();

            string[] lines = System.IO.File.ReadAllLines(filename);

            int extAddress = 0;

            foreach (String line in lines)
            {
                if (!line.StartsWith(":")) continue;
                //Console.WriteLine(line);

                byte[] bytes = new byte[(line.Length - 1) / 2];

                for (int i=0; i < bytes.Length; i++)
                {
                    bytes[i] = getByteFromHex(line.Substring(i*2+1,2));
                }

                if (bytes[0] != bytes.Length - 5 || !validateCrc(bytes))
                {
                    Console.WriteLine("CRC Error");
                    Console.WriteLine(line);
                    // error
                }
                else
                {
                    int address = (bytes[1] << 8) | (bytes[2]);

                    if (bytes[3] == 4) // ext address
                    {
                        extAddress = (bytes[4] << 24) | (bytes[5] << 16);
                    }
                    else if (bytes[3] == 0)
                    {
                        address /= 2;
                        if (address < 1024 || address >= 2048)
                            insert(bytes, address + extAddress);
                        if (address == 2048)
                        {
                            Console.WriteLine(Tools.getHexString(bytes));
                        }
                    }
                }
            }

            /*foreach (HEXBlock block in pages)
            {
                Console.WriteLine("PAGE @ " + block.address);
                for (int i = 0; i < block.rows.Length; i++)
                {
                    //Console.WriteLine("- " + Tools.getHexString(block.rows[i]));
                }
            }*/

        }

        private void insert(byte[] bytes, int address)
        {
            if (format == HEXReader.FORMAT_PIC33)
            {
                if (address > 12288) return;
                if (address >= 1024 && address < 2048) return;

                HEXBlock block = getHexBlock(address);

                for (int i = 4; i < bytes.Length - 1; i += 4)
                {
                //    Console.WriteLine(" -> Insert " + Tools.getHexString(bytes, i, 3));// + " into row " + row + " : " + offset);
                    block.insert(bytes, i, 3, address);
                    bytesTotal += 3;

                    address += 2;
                    if (address >= block.address + block.length)
                    {
                        block = getHexBlock(address);
                    }
                }
            }
        }

        public HEXBlock getBlock(int index)
        {
            if (index >= pages.Count) return null;

            return pages[index];
        }

        public byte[] getBytesAt(int address, int length)
        {
            byte[] bytes = new byte[length];

            for (int i = address; i < address + length; i++)
            {
                foreach (HEXBlock block in pages)
                {
                    if (block.address <= i && block.address + block.length > i)
                    {
                        int offset = i - block.address;
                        int offset2 = offset % block.bytesPerRow;
                        //Console.WriteLine("Found " + i + "  at page @ " + block.address + " --> ")
                        bytes[i - address] = block.rows[offset / block.bytesPerRow][offset2];
                        break;
                    }
                }
            }

            return bytes;
        }

        private bool validateCrc(byte[] bytes)
        {
            byte s = 0;
            for (int i = 0; i < bytes.Length; i++)
            {
                s += bytes[i];
            }

            if (s != 0x00) return false;

            return true;
        }

        private HEXBlock getHexBlock(int address)
        {
            address /= 1024;
            address *= 1024;
            foreach (HEXBlock h in pages)
            {
                if (h.address == address) return h;
            }

            HEXBlock n = new HEXBlock(address, format);
            pages.Add(n);
            return n;
        }
    }
}
